iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Modern Web

從 Next.js 開始的 Functional Programming系列 第 15

D15 - 實作異步流程 (一)

  • 分享至 

  • xImage
  •  

程式碼請參考 D15/asynchronous

今天講的程式碼主要會在這裡
src\app\edit\courses\all\components\add-course-form\users\feature\add-user.ts

範圍包含 步驟1 與 步驟2

1. 解析事件

https://ithelp.ithome.com.tw/upload/images/20230930/20158615U9QbUhw0fa.png

當我們輸入完畢,有兩個不同的事件會觸發新增使用者的流程

  • 按下 Enter
  • 按下 Add 按鈕

如果是鍵盤事件,我們可以藉由檢查 event.key 是否為 Enter 來判斷是否觸發後續工作流程,如果是滑鼠事件,則必定觸發後續工作流程。

所以我們可以把難以判讀的 event 轉換成兩個 literal type

  • 新增使用者事件
  • 沒事
const parseEvent = (event: unknown): 'Nothing' | 'AddUserEvent' =>
  pipe(
    Match.value(event),
    Match.when({ key: Match.string }, ({ key }) =>
      pipe(
        Match.value(key),
        Match.when('Enter', () => 'AddUserEvent' as const),
        Match.orElse(() => 'Nothing' as const)
      )
    ),
    Match.orElse(() => 'AddUserEvent' as const)
  )

這種寫法不像 boolean 等等邏輯表示式,利用簡單的 literal type,就能充分表達語意,提高可讀性。

export const addUser =
  (event: unknown) =>
  (field: UsersField): Effect.Effect<never, 'Nothing', UsersField> =>
    pipe(
      Match.value(parseEvent(event)),
      Match.when('Nothing', () => {/* do nothing */}),
      Match.when('AddUserEvent', () => {/* run subsequence workflow */}),
      Match.exhaustive
    )

2. 發出請求

所有網路通訊都會有延遲,導致我們必須進行異步處理,而且通訊過程有可能出錯,同時還可能必須存在某些外部依賴才可以進行連線。

以上三個面向相關的邏輯,雖然和程式主要運行流程不一定有直接關係,卻非常繁瑣,經常占用工程師大量時間處理,因此我們會藉由 effect 來幫助我們解決以上問題。

關於什麼是資料型別盒子,請參考 D08 - 資料型別盒子

Effect 盒子

Effect 盒子由三種 generic type 組成

type Effect<Requirements, Error, Value> = ( 
	context: Context<Requirements>
) => Error | Value
  • Requirements 有時候用 R 表示,其實就跟我們在 D12/設計依賴注入 中談到的內容具有很相似的設計思想,我們可以透過介面先假裝有某項資源或服務存在,再在程式碼的執行階段使用它,不過暫時我們還不會用到它。

  • Error 通常用 E 表示,跟 D07/軌道導向設計 中提到的 Left 是一樣的概念,通常代表錯誤或異常,也代表會離開正常的工作流程。

  • Value 有時候會用 A 表示,跟 D07/軌道驅動設計 中提到的 Right 是一樣的概念,通常代表成功執行將會產生的結果,也代表會繼續走在正常的工作流程上。

Effect.tryPromise

要如何得到一個 effect 盒子呢? 其中一個方法是使用 Effect.tryPromise

const getUser = (name: string):Effect.Effect<never, RequestError, AxiosResponse> =>
  pipe(
    Effect.tryPromise({
      try: () => axios.get(`/api/v1/users/${name}`).then((res) => res.data),
      catch: (error) => requestErrorOf(error),
    }),
  )

使用它時必須提供兩個參數

  • try : ()=>Promise<Value> 代表可能發生錯誤的異步操作
  • catch: (error: unkown)=>Error 如果發生錯誤,允許你對這個未知的錯誤做處理,轉換成型別上比較有意義的錯誤。

當提供完這兩個參數後我們就會得到 Effect.Effect<never, RequestError, Any>

  • never 表示它沒有外部依賴
  • RequestError 表示它可能會發生請求錯誤,而請求錯誤由 UnexpectedRequestErrorNotFoundError 組成
  • Any 表示我們將收到一個 任意型別 的回應

當我們看到 any 請不要開心,而是要覺得驚恐,要想盡辦法把它扼殺在搖籃之中 !!!

明天我會跟大家分享,如何把 any 變成可靠的、經過驗證的、已知的型別,敬請期待 !


上一篇
D14 - 設計異步流程
下一篇
D16 - 實作異步流程 (二)
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言